package client;

import communication.BandwidthConnection;
import communication.Connection;
import communication.ConnectionException;
import constans.CapsuleConstans;
import constans.ConfigurationConstans;
import constans.TimeConstans;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Random;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import server.ServerState;
import utils.MachineUtils;


public class Client extends Thread {


    private final static int factor = 2;
    private final static int maxTries = 5;
    private static int numberTries;
    private static long sleepTime;
    private static boolean success;
    private Configuration configuration;
    private AtomicBoolean isExecute;
    private Logger logger;
    private Identifier identifier;
    private Connection connection;


    public Client(Logger logger, Identifier identifier,
            Configuration configuration){
        initVariables(logger,identifier,configuration);
        loadConfiguration();        
    }
    

    @Override
    public void run() {        

		while(isExecute.get()){
			try {

                success = false;                


                int bandwidth = (int) (configuration.getBandwidth().getBandwidth()/10);

                logger.log(Level.INFO,"Get secure connection to rARC server");
                connection = MachineUtils.getSecureConnectionControlled(ConfigurationConstans.RARC_HOSTNAME,
                        ConfigurationConstans.RARC_PORT,bandwidth);
				
				RarcServer rarcServer = new RarcServer(connection);

                logger.log(Level.INFO,"Do login in rARC server");
				if (rarcServer.login(identifier)) {
                    
					ServerState serverState = rarcServer.getServerState();
                    Process rp;

                    if(serverState.equals(ServerState.REPLICATION)){
                       logger.log(Level.INFO,"rARC server state: Replication");
                       rp = new ReplicationProcess(rarcServer, configuration, logger, isExecute);

                    }else{
                       logger.log(Level.INFO,"rARC server state: Recovery");
                       rp = new RecoveryProcess(rarcServer,configuration, logger, isExecute);
                    }

                    rp.execute();

                    logger.log(Level.INFO,"Do logout from rARC server");
                    if(isExecute.get())
                        rarcServer.logout();

                    success = true;
                    
				}else{
                    logger.log(Level.INFO,"Problems: CAN NOT LOGIN");
                }

			}
            catch (GeneralSecurityException e) {
                logger.log(Level.SEVERE,e.getMessage());
                break;
            }
			catch (ConnectionException e) {
                logger.log(Level.WARNING,e.getMessage());
                
            }
            catch (NullPointerException e) {
                logger.log(Level.WARNING,e.getMessage());
            }
			finally{
				try {
					if(connection!=null){
						connection.close();
					}
				} catch (ConnectionException e1) {}
			}

			try {
				if(isExecute.get()){

                    setTimeSleep();
                    logger.log(Level.INFO,"Time sleep: ("+sleepTime/TimeConstans.MINUTE+") m");
                    Thread.sleep(sleepTime);
				}
			} catch (InterruptedException e) {
                logger.log(Level.WARNING,e.getMessage());
                break;				
			}
		}
    }


    public Configuration getConfiguration(){
		return configuration;
	}


	public void stopRun(){
        logger.log(Level.INFO,"rARC Client will stop");
        isExecute.set(false);
        interrupt();
        if(connection!=null){
            try {
                connection.close();
            } catch (ConnectionException ex) {}
        }
	}


    public void setOfferSpace(long offerSpace) {
        
        configuration.setOfferSpace(offerSpace);

        try {
            configuration.save(ConfigurationConstans.CONFIGURATION_FILENAME);
        } catch (IOException e) {
            logger.log(Level.WARNING,e.getMessage());
        }
	}


	public void setBandwidth(BandwidthConnection bandwidthConnection) {
 		configuration.setBandwidth(bandwidthConnection);

        try {
            configuration.save(ConfigurationConstans.CONFIGURATION_FILENAME);
        } catch (IOException e) {
            logger.log(Level.WARNING,e.getMessage());
        }
	}


    private void cleanCapsules() {

        long usedSpace;
        String [] filesnames;
        
        do{
            
            filesnames = MachineUtils.getFilesNames(ConfigurationConstans.LOCAL_DIRECTORY,
                CapsuleConstans.CAPSULE_EXTENSION);

            usedSpace = MachineUtils.getUsedSpace(filesnames);

            if(usedSpace > configuration.getOfferSpace()){
                if(MachineUtils.deleteFile(ConfigurationConstans.LOCAL_DIRECTORY, filesnames[0]))
                    logger.log(Level.INFO,"Delete capsule["+filesnames[0]+"]->OK");
                else
                    logger.log(Level.INFO,"Delete capsule["+filesnames[0]+"]->NOT OK");
            }
            
        }while(usedSpace > configuration.getOfferSpace());
    }


    private void initVariables(Logger logger, Identifier identifier, Configuration configuration) {
        this.logger = logger;
        this.identifier = identifier;
        this.configuration = configuration;
        this.isExecute = new AtomicBoolean(true);
    }

    
	private void loadConfiguration() {
        logger.log(Level.INFO,"Load configuration");

        try {
            configuration.load(Configuration.CONFIGURATION_FILENAME);
        } catch (IOException e){
            logger.log(Level.WARNING,e.getMessage());
        }


        if( configuration.getOfferSpace() < CapsuleConstans.CAPSULE_LENGTH ||
            configuration.getOfferSpace() > (MachineUtils.getTotalSpace(ConfigurationConstans.LOCAL_DIRECTORY)))
          configuration.setOfferSpace(CapsuleConstans.CAPSULE_LENGTH);


        String [] filesnames = MachineUtils.getFilesNames(ConfigurationConstans.LOCAL_DIRECTORY,
                CapsuleConstans.CAPSULE_EXTENSION);

		long usedSpace = MachineUtils.getUsedSpace(filesnames);

		if(configuration.getOfferSpace() < usedSpace){
            cleanCapsules();
        }

        try {
            configuration.save(ConfigurationConstans.CONFIGURATION_FILENAME);
        } catch (IOException e) {
            logger.log(Level.WARNING,"Problem to store configuration file: "+e.getMessage());
        }
    }


    private void setTimeSleep(){
        if (success)
        {

           sleepTime =  new Random().nextInt((int)TimeConstans.DAY);
           if(sleepTime < (TimeConstans.HOUR))
                sleepTime = TimeConstans.HOUR;            
            numberTries = 0;

        }else {
            if (numberTries < maxTries) {
                numberTries++;
                sleepTime = TimeConstans.MINUTE * (numberTries * factor);
            }
            else {
                 sleepTime   = TimeConstans.HOUR;
            }
        }
    }

}